WebGPURenderer: Surface uncaptured GPU errors and WGSL diagnostics#33418
Open
RenaudRohlinger wants to merge 4 commits intomrdoob:devfrom
Open
WebGPURenderer: Surface uncaptured GPU errors and WGSL diagnostics#33418RenaudRohlinger wants to merge 4 commits intomrdoob:devfrom
RenaudRohlinger wants to merge 4 commits intomrdoob:devfrom
Conversation
…nostics. Today a crash in WebGPU usually ends as a generic "Device Lost" with an unrecoverable-error message and no cause. This extends the error-reporting surface to use more of the spec: - Wire `device.onuncapturederror` and route it through a new `renderer.onError` callback (mirrors `renderer.onDeviceLost`), so validation / out-of-memory / internal errors raised outside an error scope are surfaced with their GPUError subclass name instead of being silently escalated to a device loss. Applications can override `renderer.onError` to handle these without crashing the device. - On render and compute pipeline creation failures, call `GPUShaderModule.getCompilationInfo()` on the stages involved and log each message with line/column plus a source excerpt and caret, turning opaque validation failures into actionable WGSL feedback. - Wrap compute pipeline creation in a validation error scope (previously only render pipelines were wrapped). - Stop swallowing rejections from `createRenderPipelineAsync` — the rejection reason is now included in the logged failure.
The previous commit guarded each fire-and-forget call site of
`_reportShaderDiagnostics` with `.catch(() => {})`. That scattered
the "never propagate" contract across three call sites and left
user-injected custom loggers (via `setConsoleFunction`) and future
edits unprotected.
Wrap the helper body in a single top-level try/catch so the contract
lives with the code it protects; drop `.catch()` guards at the call
sites.
- `_onError`: drop the leading `THREE.` from the log message. `error()` already prepends it, so uncaptured errors were logged as `THREE.THREE.WebGPURenderer: Uncaptured ...`. - Async render pipeline creation: wrap the Promise body in try/finally so `resolve()` is always called. If an unexpected throw escaped — for instance from a future await inserted above — the outer `Promise.all( promises )` in `compileAsync()` would hang forever, pinning the awaiting scene graph. Realistic paths are already safe today (`_reportShaderDiagnostics` has a top-level try/catch and `popErrorScope()` does not reject per spec), but making resolution unconditional costs nothing and removes the foot-gun. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📦 Bundle sizeFull ESM build, minified and gzipped.
🌳 Bundle size after tree-shakingMinimal build including a renderer, camera, empty scene, and dependencies.
|
RenaudRohlinger
added a commit
to RenaudRohlinger/three.js
that referenced
this pull request
Apr 19, 2026
These changes belong to mrdoob#33418 and were bundled into this branch by mistake. - Renderer.js: drop `onError` / `_onError` hook. - WebGPUBackend.js: drop `device.onuncapturederror` → `renderer.onError` bridge. - WebGPUPipelineUtils.js: revert to dev (removes pipeline-label error messages and `_reportShaderDiagnostics`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Thanks, this will improve a lot the devs & agent self-testing ! |
sunag
reviewed
Apr 21, 2026
|
|
||
| for ( const { program, module } of stages ) { | ||
|
|
||
| if ( ! module || typeof module.getCompilationInfo !== 'function' ) continue; |
Collaborator
There was a problem hiding this comment.
Sometimes, for me, LLMs add many extra checks; could that be the case? Maybe the info checks bellow is also overloaded with verification?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a WebGPU render or compute pipeline fails today, three.js logs an opaque
[Invalid ShaderModule "fragment"] is invalid, no line number, no hint at the real WGSL mistake. Downstream uncaptured errors (bad buffer writes, invalid command-buffer submits) are not surfaced through the renderer at all; they only appear as raw WebGPU entries in the browser console.This PR uses more of the spec to fix both:
renderer.onErrorcallback wired todevice.onuncapturederror, mirroringrenderer.onDeviceLost. Validation / OOM / internal errors raised outside an error scope now surface with theirGPUErrorsubclass (e.g.GPUValidationError) and message. Apps can overrideonErrorto handle them in their own UI.GPUShaderModule.getCompilationInfo()on the involved stages and log each message with line/column, source excerpt, and caret.createRenderPipelineAsyncrejections are no longer swallowed, the reason is included in the logged failure.Before:
After:
Validation errors stay recoverable (the device keeps running); only real device loss still routes through
onDeviceLost.Example:

Should I include this example?
This contribution is funded by Spawn